#include "CudaTimer.h"
#include <limits>

CSynchronizingCudaStopwatch::CSynchronizingCudaStopwatch(cudaStream_t stream) : stream(stream), timeResult(std::numeric_limits<float>::quiet_NaN())
{
	cudaEventCreate(&(this->startEvent));
	cudaEventCreate(&(this->stopEvent));
}

CSynchronizingCudaStopwatch::~CSynchronizingCudaStopwatch()
{
	cudaEventDestroy(this->startEvent);
	cudaEventDestroy(this->stopEvent);
}

void CSynchronizingCudaStopwatch::InsertStartEvent()
{
	cudaEventRecord(this->startEvent, this->stream);
}

void CSynchronizingCudaStopwatch::InsertStopEvent()
{
	cudaEventRecord(this->stopEvent, this->stream);
}

/// <summary>	Wait for the stop-event to be set and get the duration (to the start event). </summary>
///
/// <returns>	A double giving the duration in milliseconds (ms). </returns>
double CSynchronizingCudaStopwatch::SyncAndGetTiming()
{
	cudaEventSynchronize(this->stopEvent);
	cudaEventElapsedTime(&(this->timeResult), this->startEvent, this->stopEvent);
	return this->timeResult;
}

//******************************************************************************

CPerfClass::CPerfClass(cudaStream_t stream, FTLib::IPerformanceDataStore* perfData)
	: currentCudaEvent(0), perfData(perfData), stream(stream)
{}

void CPerfClass::ReserveEvents(int count)
{
	if (!this->events.empty())
	{
		throw std::logic_error("Method may only be called once and before events have been inserted.");
	}

	this->events.reserve(count);
	for (int i = 0; i < count; ++i)
	{
		cudaEvent_t event;
		cudaEventCreate(&event);
		this->events.push_back(event);
	}
}

int CPerfClass::InsertCudaEvent()
{
	if (this->currentCudaEvent >= this->events.size())
	{
		cudaEvent_t event;
		cudaEventCreate(&event);
		this->events.push_back(event);
	}

	cudaEventRecord(this->events[this->currentCudaEvent], this->stream);

	return this->currentCudaEvent++;
}

void CPerfClass::SychnronizeToEvent(int eventNo)
{
	cudaEventSynchronize(this->events[eventNo]);
}

void CPerfClass::ReportDurationBetweenEvent(int eventFirst, int eventSecond, const wchar_t* str)
{
	float t;
	cudaEventElapsedTime(&t, this->events[eventFirst], this->events[eventSecond]);
	this->perfData->AddPerformanceData(str, t);
}

void CPerfClass::StartWallclock()
{
	this->wallclockStopWatch.startTimer();
}

void CPerfClass::StopWallclock()
{
	this->wallclockStopWatch.stopTimer();
}

void CPerfClass::ReportDurationWallclock(const wchar_t* str)
{
	double t = this->wallclockStopWatch.getElapsedTime() * 1000;
	this->perfData->AddPerformanceData(str, t);
}